package com.studerb.nntp;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.net.SocketFactory;
import org.apache.commons.net.nntp.NNTP;
import org.apache.commons.net.nntp.NNTPCommand;
import org.apache.commons.net.nntp.NNTPReply;
import org.apache.log4j.Logger;
import org.bushe.swing.event.EventService;
import org.springframework.beans.factory.annotation.Autowired;

import com.studerb.events.NntpConnectionEvent;
import com.studerb.model.Server;

/**
 * @author studerb Extends
 * @{link org.apache.commons.net.nntp.NNTP}
 * adding a staticly defined and unique name
 * for each instance created by Spring. Also
 * publishes events on connection,
 * disconnection command, exception, and
 * group changes. Events are fired by topic
 * (topic being the same as the connection's
 * name). Publishes NetworkMonitorEvents when
 * encountering exceptions due to network
 * connection (not errors due to specific
 * nntp commands).
 * @see com.studerb.events.NntpConnectionEvent
 * @see com.studerb.service.EventService
 * @see com.studerb.events.NetworkMonitorEvent
 */
public class NntpConnection extends NNTP {
	protected static AtomicInteger connName = new AtomicInteger();
	private static Logger logger = Logger.getLogger(NntpConnection.class);
	private String name;
	private String currentGroup;
	protected Server server;
	@Autowired EventService eventService;

	public NntpConnection(SocketFactory socketFactory) {
		setSocketFactory(socketFactory);
		name = "NntpConnection_" + String.valueOf(connName.getAndIncrement());
	}

	private void _authenticate() throws IOException {
		int replyCode = authinfoUser(server.getUserName());
		if (replyCode == NNTPReply.MORE_AUTH_INFO_REQUIRED) {
			replyCode = authinfoPass(server.getPassword());
			if (replyCode != NNTPReply.AUTHENTICATION_ACCEPTED) {
				throw new IOException("Error authenticating to " + server.getHost());
			}
		}
		else {
			throw new IOException("Error authenticating to " + server.getHost());
		}
	}

	@Override protected void _connectAction_() throws IOException {
		super._connectAction_();
		if (this.getReplyCode() != NNTPReply.SERVER_READY_POSTING_ALLOWED && this.getReplyCode() != NNTPReply.SERVER_READY_POSTING_NOT_ALLOWED) {
			// TODO send message
			this.disconnect();
			throw new IOException("Error Connecting: " + getReplyString());
		}
		if (isConnected()) {
			if (server.isAuthRequired()) {
				this._authenticate();
			}
		}
		else {
			throw new IOException("Error Connecting: " + getReplyString());
		}
	}

	public void connect() throws IOException {
		logger.debug("connecting : " + " to host: " + server.getHost() + " on port: " + server.getPort());
		NntpConnectionEvent event = null;
		try {
			super.connect(server.getHost(), server.getPort());
			event = new NntpConnectionEvent(this, NntpConnectionEvent.EventType.CONNECTED);
		}
		catch (IOException e) {
			event = new NntpConnectionEvent(this, NntpConnectionEvent.EventType.EXCEPTION, e);
			throw e;
		}
		finally {
			eventService.publish(this.getName(), event);
		}
	}

	/*
	 * public void destroy() {
	 * System.err.println("***Destroying NNTP Connection on Spring Exit"); if
	 * (this.isConnected()) { this.disconnect(); } }
	 */

	@Override public void disconnect() {
		NntpConnectionEvent event;
		boolean loggedError = false;
		try {
			if (isConnected()) {
				logger.debug("conn: " + " sending quit command");
				super.quit();
			}
		}
		catch (Exception e) {
			logger.error("conn sending quit command: " + ExceptionUtils.getRootCauseMessage(e));
			loggedError = true;
		}

		try {
			if (isConnected()) {
				super.disconnect();
				logger.debug("conn: " + "disconnecting");
			}
		}
		catch (Exception e) {
			if (!loggedError) {
				logger.warn("conn disconnecting: " + ExceptionUtils.getRootCauseMessage(e));
			}
		}

		event = new NntpConnectionEvent(this, NntpConnectionEvent.EventType.DISCONNECTED);
		eventService.publish(this.getName(), event);
	}

	public long getByteCount() {
		return countingInputStream.getByteCount();
	}

	public String getCurrentGroup() {
		return currentGroup;
	}

	public String getLastCommand() {
		return lastCommand;
	}

	public String getName() {
		return name;
	}

	public BufferedReader getReader() {
		return _reader_;
	}

	/*
	 * public void setCurrentGroup(String group) { synchronized(this){
	 * currentGroup = group; } NntpConnectionEvent event = new
	 * NntpConnectionEvent(this, NntpConnectionEvent.EventType.GROUP);
	 * eventService.publish(this.getName(), event); }
	 */

	public Server getServer() {
		return server;
	}

	public BufferedWriter getWriter() {
		return _writer_;
	}

	public GroupCommandReply groupCommand(String newsgroup) throws IOException {
		logger.debug("Sending group command");
		int groupReply = sendCommand(NNTPCommand.GROUP, newsgroup);
		if (groupReply != NNTPReply.GROUP_SELECTED) {
			throw new IOException("Bad GROUP reply: " + getReplyString());
		}
		GroupCommandReply groupCommandReply = GroupCommandReply.parseGroupCommandReply(getReplyString());
		if (groupCommandReply == null) {
			throw new IOException("Bad GROUP reply: " + getReplyString());
		}
		currentGroup = newsgroup;
		NntpConnectionEvent evt = new NntpConnectionEvent(this, NntpConnectionEvent.EventType.GROUP);
		evt.setGroupCommandReply(groupCommandReply);
		eventService.publish(evt);
		return groupCommandReply;
	}

	@Override public int sendCommand(int command) throws IOException {
		return this.sendCommand(command, null);
	}

	@Override public int sendCommand(int command, String args) throws IOException {
		NntpConnectionEvent event = null;
		try {
			int reply = super.sendCommand(command, args);
			event = new NntpConnectionEvent(this, NntpConnectionEvent.EventType.COMMAND);
			return reply;
		}
		catch (IOException e) {
			event = new NntpConnectionEvent(this, NntpConnectionEvent.EventType.EXCEPTION, e);
			throw e;
		}
		finally {
			eventService.publish(this.getName(), event);
		}
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setServer(Server server) {
		this.server = server;
	}

	@Override public String toString() {
		return new ToStringBuilder(this).append("name", name).append("Server", this.getServer().getHost()).append("isConnected", this.isConnected()).toString();
	}
}
